/**
 * Used to configure the global error handling function, which can monitor vue errors, script errors, static resource errors and Promise errors
 */

import type { ErrorLogInfo } from '/#/store';

import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog';

import { ErrorTypeEnum } from '/@/enums/exceptionEnum';
import { App } from 'vue';
import projectSetting from '/@/settings/projectSetting';

/**
 * 根据错误对象生成堆栈信息字符串
 * @param error 错误对象
 * @returns 错误堆栈信息字符串
 */
function processStackMsg(error: Error): string {
  if (!error.stack) {
    return '';
  }
  let stack = error.stack
    .replace(/\n/gi, '') // 移除换行符以减少传输内容的大小
    .replace(/\bat\b/gi, '@') // Chrome中的 "at"，Firefox中的 "@"
    .split('@') // 使用 @ 分割信息
    .slice(0, 9) // 最大堆栈长度 (Error.stackTraceLimit = 10)，只取前10个
    .map((v) => v.replace(/^\s*|\s*$/g, '')) // 移除额外的空格
    .join('~') // 手动添加分隔符以便后续显示
    .replace(/\?[^:]+/gi, ''); // 移除js文件链接中的冗余参数（如 ?x=1 等）
  const msg = error.toString();
  if (stack.indexOf(msg) < 0) {
    stack = msg + '@' + stack;
  }
  return stack;
}

/**
 * get comp name
 * @param vm
 */
function formatComponentName(vm: any) {
  if (vm.$root === vm) {
    return {
      name: 'root',
      path: 'root',
    };
  }

  const options = vm.$options as any;
  if (!options) {
    return {
      name: 'anonymous',
      path: 'anonymous',
    };
  }
  const name = options.name || options._componentTag;
  return {
    name: name,
    path: options.__file,
  };
}

/**
 * Vue 错误处理函数
 * @param err 错误对象
 * @param vm Vue 实例对象
 * @param info 错误信息
 */
function vueErrorHandler(err: unknown, vm: any, info: string): void {
  const errorLogStore = useErrorLogStoreWithOut();
  const { name, path } = formatComponentName(vm);
  const error = err as Error;

  // 构造错误日志信息
  errorLogStore.addErrorLogInfo({
    type: ErrorTypeEnum.VUE, // 错误类型为 Vue 错误
    name, // 组件名称
    file: path, // 文件路径
    message: error.message, // 错误消息
    stack: processStackMsg(error), // 错误堆栈信息
    detail: info, // 错误详细信息
    url: window.location.href, // 当前页面的 URL
  });
}

/**
 * 脚本错误处理函数
 * @param event 事件对象或错误消息
 * @param source 脚本源文件路径
 * @param lineno 错误发生的行号
 * @param colno 错误发生的列号
 * @param error 错误对象
 * @returns 是否成功处理了错误
 */
export function scriptErrorHandler(
  event: Event | string,
  source?: string,
  lineno?: number,
  colno?: number,
  error?: Error,
): boolean {
  if (event === 'Script error.' && !source) {
    return false;
  }
  const errorInfo: Partial<ErrorLogInfo> = {};
  colno = colno || (window.event && (window.event as any).errorCharacter) || 0;
  errorInfo.message = event as string;
  if (error?.stack) {
    errorInfo.stack = error.stack;
  } else {
    errorInfo.stack = '';
  }
  const name = source ? source.substring(source.lastIndexOf('/') + 1) : 'script';
  const errorLogStore = useErrorLogStoreWithOut();

  // 构造错误日志信息
  errorLogStore.addErrorLogInfo({
    type: ErrorTypeEnum.SCRIPT, // 错误类型为脚本错误
    name: name, // 错误源名称
    file: source as string, // 错误源文件路径
    detail: 'lineno' + lineno, // 错误行号详情
    url: window.location.href, // 当前页面的 URL
    ...(errorInfo as Pick<ErrorLogInfo, 'message' | 'stack'>), // 错误消息和堆栈信息
  });

  return true;
}

/**
 * 注册 Promise 错误处理函数
 */
function registerPromiseErrorHandler(): void {
  //unhandledrejection 是一个事件名称，当 Promise 被拒绝并且没有被 catch 或处理时，就会触发 unhandledrejection 事件。
  window.addEventListener(
    'unhandledrejection',
    function (event) {
      const errorLogStore = useErrorLogStoreWithOut();

      // 构造错误日志信息
      errorLogStore.addErrorLogInfo({
        type: ErrorTypeEnum.PROMISE, // 错误类型为 Promise 错误
        name: 'Promise Error!', // 错误名称
        file: 'none', // 文件路径为空
        detail: 'promise error!', // 错误详情
        url: window.location.href, // 当前页面的 URL
        stack: 'promise error!', // 堆栈信息为 "promise error!"
        message: event.reason, // 错误消息为 Promise rejection 的原因
      });
    },
    true,
  );
}

/**
 * 注册资源加载错误处理函数
 */
function registerResourceErrorHandler(): void {
  // 监听资源加载错误（如图片、脚本、样式表和 jsonp）
  window.addEventListener(
    'error',
    function (e: Event) {
      const target = e.target ? e.target : (e.currentTarget as any);
      const errorLogStore = useErrorLogStoreWithOut();

      // 构造错误日志信息
      //使用{} as any 表示该对象的类型是任意类型，这意味着 TypeScript 不会再对它进行类型检查和属性检查。
      errorLogStore.addErrorLogInfo({
        type: ErrorTypeEnum.RESOURCE, // 错误类型为资源加载错误
        name: 'Resource Error!', // 错误名称
        file: (e.target || ({} as any)).currentSrc, // 当前资源的路径，加({} as any)的目的是为了消除编译器的警告
        detail: JSON.stringify({
          tagName: target.localName, // 当前标签的本地名称
          html: target.outerHTML, // 当前标签的 HTML 内容
          type: e.type, // 事件类型
        }),
        url: window.location.href, // 当前页面的 URL
        stack: 'resource is not found', // 堆栈信息为 "resource is not found"
        message: (e.target || ({} as any)).localName + ' is load error', // 错误消息为加载错误的元素的本地名称
      });
    },
    true,
  );
}

/**
 * 设置全局错误处理
 * @param app 应用实例
 */
export function setupErrorHandle(app: App): void {
  const { useErrorHandle } = projectSetting;

  // 如果不启用错误处理，则直接返回
  if (!useErrorHandle) {
    return;
  }

  // Vue 异常监控
  app.config.errorHandler = vueErrorHandler;

  // 监听脚本错误
  window.onerror = scriptErrorHandler;

  // 监听 Promise 异常
  registerPromiseErrorHandler();

  // 监听静态资源异常
  registerResourceErrorHandler();
}
